/**
 *
 */
package com.honeybees.framework.common.util;

import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

import java.io.BufferedReader;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * <dl>
 * <dt><b> 关于 网络 的工具类 </b></dt>
 * <p>
 * <dd>功能描述</dd>
 * </dl>
 * <p>
 * Copyright (C) All rights reserved.
 * </p>
 *
 * @author 李远明
 * @version 2015-12-22 18:08:55
 */
public final class HTTPUtils {

    /**
     * <dl>
     * <dt><b> Linux系统下，获取IP地址 </b></dt>
     * <p>
     * <dd>功能描述</dd>
     * </dl>
     *
     * @param macName
     * @return
     * @throws SocketException
     * @throws UnknownHostException
     * @author 李远明
     * @version 2015-12-30 11:32:37
     */
    public static String getLinuxIP(String macName) throws SocketException, UnknownHostException {
        Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();

        while (true) {
            NetworkInterface ni;
            do {
                if (!en.hasMoreElements()) {
                    return null;
                }
                ni = en.nextElement();
            } while (!ni.getName().equals(macName));

            List<InterfaceAddress> list = ni.getInterfaceAddresses();
            for (InterfaceAddress ia : list) {
                if (ia.getNetworkPrefixLength() == 24) {
                    return ia.getAddress().getHostAddress();
                }
            }
        }
    }

    /**
     * <dl>
     * <dt><b> Windows系统下，获取IP地址 </b></dt>
     * <p>
     * <dd>功能描述</dd>
     * </dl>
     *
     * @return IP地址
     * @throws SocketException
     * @throws UnknownHostException
     * @author 李远明
     * @version 2015-12-30 11:32:54
     */
    public static String getWindowsIP() throws SocketException, UnknownHostException {
        return InetAddress.getLocalHost().getHostAddress();
    }

    public static void setNoCacheHeader(HttpServletResponse response) {
        response.addHeader("Cache-Control", "no-store");
        response.addHeader("Cache-Control", "no-cache");
        response.addHeader("Pragma", "no-cache");
        response.setDateHeader("Expires", 0L);
    }

    /**
     * <dl>
     * <dt><b> 设置 HTTP-only Cookie </b></dt>
     * <p>
     * <dd>功能描述</dd>
     * </dl>
     *
     * @param response 客户端响应
     * @param name     Cookie名称（键）
     * @param value    值
     * @param path     路径
     * @param domain   域名
     * @param maxAge   存储时间
     * @param secure   是否只有HTTPS请求才传递到服务器端，
	 *					设置 secure 为 true，Cookie 信息将不会在 HTTP 连接中传输，一定程度上防范 XSS 攻击。
     * @author 李远明
     * @version 2015-12-30 11:33:16
     */
    public static void setHttpOnlyCookie(HttpServletResponse response, String name, String value, String path,
                                         String domain, int maxAge, boolean secure) {
        // StringBuilder sb = new StringBuilder(name.trim() + "=" + value);
        // sb.append(isBlank(path) ? "/" : ";Path=" + path);
        // sb.append(isBlank(domain) ? "" : ";Domain=" + domain);
        // sb.append(maxAge == -1 ? "" : ";Max-Age=" + maxAge);
        // sb.append(";HTTPOnly");
        // String cookie = sb.toString();
        // response.addHeader("Set-Cookie", cookie);
        Cookie cookie = new Cookie(name, value);
        cookie.setPath(isBlank(path) ? "/" : path);
        cookie.setDomain(isBlank(domain) ? "" : domain);
        cookie.setMaxAge(maxAge);
        cookie.setSecure(secure);
		// 设置 HttpOnly 为 true，将可以禁止 JavaScript 读取页面 Cookie 信息，一定程度上防范 XSS 攻击。
        cookie.setHttpOnly(true);
        response.addCookie(cookie);
    }

    public static String getRealIP(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (isInvalidIP(ip)) {
            ip = request.getHeader("X-Forwarded-For");
        }
        if (isInvalidIP(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (isInvalidIP(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (isInvalidIP(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (isInvalidIP(ip)) {
            ip = request.getRemoteAddr();
        }
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
    }

    /**
     * <dl>
     * <dt><b> IP地址是否无效 </b></dt>
     * <p>
     * <dd>功能描述</dd>
     * </dl>
     *
     * @param ip IP地址
     * @return
     */
    private static boolean isInvalidIP(String ip) {
        return ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip);
    }

    /**
     * Checks if the specified string is a valid IP address.
     *
     * @param ip the string to validate
     * @return true if the string validates as an IP address
     * @version 2017-03-12 12:17:21
     */
    public boolean isValidIP(String ip) {
        return isValidIPv4(ip) || isValidIPv6(ip);
    }

    /**
     * 判断IPv4地址是否是为合法地址
     * <p>
     * http://blog.csdn.net/zitong_ccnu/article/details/47393231
     * </p>
     *
     * @param ipv4 IPv4 地址
     * @return true/false
     * @version 2017-03-11 22:10:11 新建
     * @version 2017-03-12 12:07:26 commons-validator
     * InetAddressValidator.isValidInet4Address(String)
     */
    public static boolean isValidIPv4(String ipv4) {
        if (ipv4 == null || ipv4.isEmpty()) {
            return false;
        }
        ipv4 = ipv4.trim();
        if (ipv4.length() < 7 || ipv4.length() > 15) { // 8.8.8.8, 255.255.255.255
            return false;
        }
        String ipv4Regex = "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$";
        Pattern pattern = Pattern.compile(ipv4Regex);
        Matcher matcher = pattern.matcher(ipv4);
        String[] groups = null;
        if (matcher.matches()) {
            int count = matcher.groupCount();
            groups = new String[count];
            for (int j = 0; j < count; j++) {
                groups[j] = matcher.group(j + 1);
            }
        }
        if (groups == null) {
            return false;
        }

        for (String ipSegment : groups) { // verify that address subgroups are legal
            if (ipSegment == null || ipSegment.length() == 0) {
                return false;
            }
            int iIpSegment = 0;
            try {
                iIpSegment = Integer.parseInt(ipSegment);
            } catch (NumberFormatException e) {
                return false;
            }
            if (iIpSegment > 255) {
                return false;
            }
            if (ipSegment.length() > 1 && ipSegment.startsWith("0")) {
                return false;
            }
        }
        return true;
    }

    /**
     * 判断IPv6地址是否是为合法地址
     * <p>
     * Internet Protocol Version 6
     *
     * @param ipv6 IPv6 地址
     * @return true/false
     * @author 李远明
     * @version 2017-03-12 12:09:33 从 commons-validator
     * InetAddressValidator.isValidInet6Address(String) 复制过来
     */
    // https://www.douban.com/group/topic/17827231/
    public static boolean isValidIPv6(String ipv6) {
        if (ipv6 == null || ipv6.isEmpty()) {
            return false;
        }
        ipv6 = ipv6.trim();
        boolean containsCompressedZeroes = ipv6.contains("::");
        if (containsCompressedZeroes && (ipv6.indexOf("::") != ipv6.lastIndexOf("::"))) {
            return false;
        }
        if ((ipv6.startsWith(":") && !ipv6.startsWith("::")) || (ipv6.endsWith(":") && !ipv6.endsWith("::"))) {
            return false;
        }

        // Max number of hex groups (separated by :) in an IPV6 address
        final int IPV6_MAX_HEX_GROUPS = 8;
        // Max hex digits in each IPv6 group
        final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4;
        final int MAX_UNSIGNED_SHORT = 0xffff;

        String[] octets = ipv6.split(":");
        if (containsCompressedZeroes) {
            List<String> octetList = new ArrayList<>(Arrays.asList(octets));
            if (ipv6.endsWith("::")) {
                // String.split() drops ending empty segments
                octetList.add("");
            } else if (ipv6.startsWith("::") && !octetList.isEmpty()) {
                octetList.remove(0);
            }
            octets = octetList.toArray(new String[octetList.size()]);
        }
        if (octets.length > IPV6_MAX_HEX_GROUPS) {
            return false;
        }
        int validOctets = 0;
        int emptyOctets = 0; // consecutive empty chunks
        for (int index = 0, l = octets.length; index < l; index++) {
            String octet = octets[index];
            if (octet.length() == 0) {
                emptyOctets++;
                if (emptyOctets > 1) {
                    return false;
                }
            } else {
                emptyOctets = 0;
                // Is last chunk an IPv4 address?
                if (index == l - 1 && octet.contains(".")) {
                    if (!isValidIPv4(octet)) {
                        return false;
                    }
                    validOctets += 2;
                    continue;
                }
                if (octet.length() > IPV6_MAX_HEX_DIGITS_PER_GROUP) {
                    return false;
                }
                int octetInt = 0;
                try {
                    octetInt = Integer.parseInt(octet, 16);
                } catch (NumberFormatException e) {
                    return false;
                }
                if (octetInt < 0 || octetInt > MAX_UNSIGNED_SHORT) {
                    return false;
                }
            }
            validOctets++;
        }
        return !(validOctets > IPV6_MAX_HEX_GROUPS || (validOctets < IPV6_MAX_HEX_GROUPS && !containsCompressedZeroes));
    }

    public static String getRealScheme(HttpServletRequest request) {
        String proto = request.getHeader("X-Forwarded-Proto");
        return isNotBlank(proto) ? proto : request.getScheme();
    }

    public static String getRealBasePath(HttpServletRequest request) {
        String port = ":" + request.getServerPort();
        String schema = getRealScheme(request);
        if ("https".equals(schema) && request.getServerPort() == 443
                || "http".equals(schema) && request.getServerPort() == 80) {
            port = "";
        }
        return schema + "://" + request.getServerName() + port;
    }

    public static String getRealMethod(HttpServletRequest request) {
        String method = request.getMethod().toUpperCase();
        String _method = request.getParameter("_method");
        if (isNotBlank(_method)) {
            _method = _method.trim().toUpperCase();
            if ("DELETE".equals(_method)) {
                method = "DELETE";
            } else if ("PUT".equals(_method)) {
                method = "PUT";
            }
        }
        return method;
    }

    /**
     * <dl>
     * <dt><b> 清除 跨站脚本攻击(Cross Site Scripting) </b></dt>
     * <p>
     * <dd>跨站脚本攻击(Cross Site Scripting)，为不和层叠样式表(Cascading Style Sheets,
     * CSS)的缩写混淆，故将跨站脚本攻击缩写为XSS。恶意攻击者往Web页面里插入恶意Script代码，当用户浏览该页之时，嵌入其中Web里面的Script代码会被执行，
     * 从而达到恶意攻击用户的特殊目的。</dd>
     * </dl>
     *
     * @param value HTTP参数值
     * @return 安全的HTTP参数值
     * @author 李远明
     * @version 2016-01-05 14:47:25
     */
    public static String trimXss(String value) {
        if (isBlank(value)) {
            return value;
        } else {
            value = value.replaceAll("", "");
            Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", 2);
            value = scriptPattern.matcher(value).replaceAll("");
            scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", 42);
            value = scriptPattern.matcher(value).replaceAll("");
            scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", 42);
            value = scriptPattern.matcher(value).replaceAll("");
            scriptPattern = Pattern.compile("</script>", 2);
            value = scriptPattern.matcher(value).replaceAll("");
            scriptPattern = Pattern.compile("<script(.*?)>", 42);
            value = scriptPattern.matcher(value).replaceAll("");
            scriptPattern = Pattern.compile("eval\\((.*?)\\)", 42);
            value = scriptPattern.matcher(value).replaceAll("");
            scriptPattern = Pattern.compile("expression\\((.*?)\\)", 42);
            value = scriptPattern.matcher(value).replaceAll("");
            scriptPattern = Pattern.compile("javascript:", 2);
            value = scriptPattern.matcher(value).replaceAll("");
            scriptPattern = Pattern.compile("vbscript:", 2);
            value = scriptPattern.matcher(value).replaceAll("");
            scriptPattern = Pattern.compile("onload(.*?)=", 42);
            value = scriptPattern.matcher(value).replaceAll("");

            return value.replaceAll("script", "").replaceAll("exec", "").replaceAll("\'", "\"").replaceAll("<", "&lt;")
                    .replaceAll(">", "&gt;").replaceAll("eval", "").replaceAll("cookie", "").replaceAll("\\(", "（")
                    .replaceAll("\\)", "）");
        }
    }

    /**
     * <dl>
     * <dt><b> 获取 用户代理 </b></dt>
     * <p>
     * <dd>功能描述</dd>
     * </dl>
     *
     * @param request 请求
     * @return navigator.userAgent
     * @author 李远明
     * @version 2015-12-31 15:06:55
     */
    public static String getBrowserAgent(HttpServletRequest request) {
        return request.getHeader("User-Agent");
    }

    /**
     * <dl>
     * <dt><b> 获取 HttpServletRequest 的参数 </b></dt>
     * <p>
     * <dd>功能描述</dd>
     * </dl>
     *
     * @param request HttpServletRequest类
     * @return id=526550660127248385&_=19238339393
     * @author 李远明
     * @version 2017-02-22 12:16:40
     * @see javax.servlet.http.HttpServletRequest#getQueryString()
     */
    public static String getRequestParameter(HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        Set<Entry<String, String[]>> entrySet = parameterMap.entrySet();

        StringBuilder sb = new StringBuilder();
        for (Entry<String, String[]> entry : entrySet) {
            String name = entry.getKey();
            String value = request.getParameter(name);
            String val = trimXss(value);
            sb.append('&');
            sb.append(name);
            sb.append('=');
            sb.append(val);
        }
        return sb.length() > 1 ? sb.substring(1) : "";
    }

    /**
     * <dl>
     * <dt><b> 判断一个请求是否为Ajax请求（jQuery？） </b></dt>
     * <p>
     * <dd>如果一个会话已经超时，但是此时再通过ajax请求，那么ajax返回的则是一个登陆页面的html，那是非常不合理的。</dd>
     * </dl>
     *
     * @param request 请求
     * @return 如果一个请求的头部包含 X-Requested-With，且值为 XMLHttpRequest，则为Ajax请求，此时返回 true；否则，为传统同步请求，返回
     * false
     * @author 李远明
     * @version 2017-03-04 22:22:24
     * @version 2017-03-05 15:09:06 static
     * @version 2017-03-05 17:59:47 {@link org.apache.coyote.Request#getHeader(String)}
     */
    // 篡改浏览器内核怎么办？
    // 如果是ajax请求的话，对应请求头信息的referer参数为当前页面的url！
    // === MimeHeaders === 2017-03-05 17:58:43
    // host = localhost:8080
    // connection = keep-alive
    // accept = application/json, text/javascript, */*; q=0.01
    // x-requested-with = XMLHttpRequest
    // user-agent = ...
    // referer = http://localhost:8080/sysmgmt/login
    // accept-encoding = gzip, deflate, sdch, br
    // accept-language = zh-CN,zh;q=0.8
    // cookie = JSESSIONID=689A6920621AF00FF3F6D063B9421447; ...
    public static boolean isAjaxRequest(HttpServletRequest request) {
        String requestType = request.getHeader("X-Requested-With");
        return "XMLHttpRequest".equals(requestType);
    }

    /**
     * <dl>
     * <dt><b> 获取原始AJAX请求的数据 </b></dt>
     * <p>
     * <dd>Content-Type: text/plain;charset=UTF-8</dd>
     * <dd>Request Playload: {mobile: "15898989899"}</dd>
     * </dl>
     * http://blog.csdn.net/mhmyqn/article/details/25561535
     *
     * @param request 当前请求
     * @return （JSON）数据，如 {"mobile":"15898989899"}
     * @author 李远明
     * @version 2017-03-09 15:51:39
     */
    public static String getRequestPayload(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = request.getReader()) {
            char[] buff = new char[1024];
            int len;
            while ((len = reader.read(buff)) != -1) {
                sb.append(buff, 0, len);
            }
        } catch (IOException e) {
            return "";
        }
        return sb.toString();
    }

    public static void main(String[] args) throws Exception {
        String ss = "http://localhost:8080/sysmgmt/menu/tree?id=526550660127248385&_=19238339393xsa";
        String str = URLEncoder.encode(ss, "UTF-8");
        System.out.println(str);

        String ipv4 = "192.168.8.26";
        boolean validIPv4 = isValidIPv4(ipv4);
        System.out.println(validIPv4);
        String ipv6 = "fe80::e02f:ba4a:d06f:fde7%12"; // false
        ipv6 = "2001:0410::fb00:1400:5000:45ff"; // true
        boolean validIPv6 = isValidIPv6(ipv6);
        System.out.println(validIPv6);
    }

}
